﻿/*************************************************************************
 * Created by OhZee Creative, LLC
 * 2013
 * This code is licensed under Microsoft Public License (Ms-PL)
 * http://opensource.org/licenses/ms-pl
 * http://ozframework.codeplex.com/
 *************************************************************************/

namespace OhZeeCreative.Helpers
{
	using System;
    using System.Collections.Generic;
    using System.Linq;

#if SILVERLIGHT
    using System.Windows;
    using System.Windows.Media;
#endif
#if NETFX_CORE
    using Windows.Foundation;
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Media;
#endif

    public static class LayoutHelper
	{
		public static Point GetOffsetPoint(Point point, Point offset, double scaleX = 1.0, double scaleY = 1.0)
		{
			return new Point((point.X + offset.X) * scaleX, (point.Y + offset.Y) * scaleY);
		}

		public static Size GetSizeAtAngle(Size size, double angleInDegrees)
		{
			if (double.IsNaN(size.Width) || double.IsInfinity(size.Width) ||
				double.IsNaN(size.Height) || double.IsInfinity(size.Height))
			{
				return size;
			}

			double angle = MathHelper.Radians(angleInDegrees);
			double cosAngle = Math.Abs(Math.Cos(angle));
			double sinAngle = Math.Abs(Math.Sin(angle));

			return new Size(size.Width * cosAngle + size.Height * sinAngle, size.Width * sinAngle + size.Height * cosAngle);
		}

        public static Point Rotate(Point point, double angleInRadians, bool positiveAngleIsClockwise = true)
        {
            if (!positiveAngleIsClockwise)
            {
                angleInRadians = -angleInRadians;
            }

            double cosAngle = Math.Cos(angleInRadians);
            double sinAngle = Math.Sin(angleInRadians);

#if SILVERLIGHT
            Point newPoint = new Point();
#endif
#if NETFX_CORE
            Point newPoint;
#endif

            // Use matrix multiplication as descripbed in Programming Windows 6th Ed., page 414         
            newPoint.X = point.X * cosAngle - point.Y * sinAngle;
            newPoint.Y = point.X * sinAngle + point.Y * cosAngle;

            return newPoint;
        }

        /// <summary>
        /// When rectangle A is rotated, it's bound by a rectangle B whose size is at least as big of A's.
        /// This function returns the top-left corner of the Rectangle B relative to the specified origin.
        /// </summary>
        
        public static Point GetLeftTopAtAngle(Size size, double angleInDegrees, Point rotationOrigin)
        {
            double angle = MathHelper.Radians(angleInDegrees);

            Point originOffet = new Point(size.Width * rotationOrigin.X, size.Height * rotationOrigin.Y);

            // Translate the corners so that the origin is in the center. 
            // Name the corners as such: Start from top-left, go clockwise: A -> B -> C -> D
            Point[] points = new Point[]
            {
                new Point(         0 - originOffet.X,           0 - originOffet.Y), // A
                new Point(size.Width - originOffet.X,           0 - originOffet.Y), // B
                new Point(size.Width - originOffet.X, size.Height - originOffet.Y), // C
                new Point(         0 - originOffet.X, size.Height - originOffet.Y), // D
            };

            // Rotate the points
            for (int i = 0; i < points.Length; i++)
            {
                points[i] = Rotate(points[i], angle, positiveAngleIsClockwise: false);
            }

            double minX = points.Min(point => point.X);
            double minY = points.Min(point => point.Y);
            
            return new Point(minX, minY);
        }


        public static Point GetParentRelativeLocation(UIElement element)
		{
#if SILVERLIGHT
			Point zeroPoint = new Point();
#endif
#if NETFX_CORE
			Point zeroPoint;
#endif
            UIElement parentElement = VisualTreeHelper.GetParent(element) as UIElement;
			GeneralTransform generalTransform = element.TransformToVisual(parentElement);
#if SILVERLIGHT
            return generalTransform.Transform(zeroPoint);
#endif
#if NETFX_CORE
			return generalTransform.TransformPoint(zeroPoint);
#endif
		}

        // Note: GetAncestor<Page>(someAppBarButton); doesn't work
        public static T GetAncestor<T>(FrameworkElement element) where T : FrameworkElement
        {
            Type ancestorType = typeof(T);
            do
            {
                element = VisualTreeHelper.GetParent(element) as FrameworkElement;

                if (element != null && Type.Equals(element.GetType(), ancestorType))
                {
                    return (T)element;
                }

            } while (element != null);

            return null;
        }

        public static void GetVisualDescendants<T>(FrameworkElement element, IList<T> descendants) where T : FrameworkElement
        {
            Type descendantType = typeof(T);
            
            int childCount = VisualTreeHelper.GetChildrenCount(element);

            for (int index = 0; index < childCount; index++)
            {
                FrameworkElement child = VisualTreeHelper.GetChild(element, index) as FrameworkElement;

                if (child != null)
                {
                    if (Type.Equals(child.GetType(), descendantType))
                    {
                        descendants.Add((T)child);
                    }

                    GetVisualDescendants<T>(child, descendants);
                }
            }
        }

        public static bool IsActuallyVisible(UIElement element)
        {
            do
            {
                if (element.Visibility != Visibility.Visible)
                {
                    return false;
                }

                element = VisualTreeHelper.GetParent(element) as UIElement;
                                
            } while (element != null);

            return true;
        }

		public static double MaximumX(Point[] points1, Point[] points2)
		{
			return Math.Max(points1.Max(point => point.X), points2.Max(point => point.X));
		}

		public static double MaximumY(Point[] points1, Point[] points2)
		{
			return Math.Max(points1.Max(point => point.Y), points2.Max(point => point.Y));
		}

		public static double MinimumX(Point[] points1, Point[] points2)
		{
			return Math.Min(points1.Min(point => point.X), points2.Min(point => point.X));
		}

		public static double MinimumY(Point[] points1, Point[] points2)
		{
			return Math.Min(points1.Min(point => point.Y), points2.Min(point => point.Y));
		}


		public static void SetRotation(UIElement element, double angleInDegrees)
		{
			SetRotation(element, angleInDegrees, element.RenderTransformOrigin);
		}

		public static void SetRotation(UIElement element, double angleInDegrees, Point transformOrigin)
		{
			element.RenderTransformOrigin = transformOrigin;

			// Go counter-clockwise to be consistent with everything else
			angleInDegrees = -angleInDegrees;

			RotateTransform transform = element.RenderTransform as RotateTransform;
			if (transform != null)
			{
				transform.Angle = angleInDegrees;
			}
			else
			{
				CompositeTransform compositeTransform = element.RenderTransform as CompositeTransform;
				if (compositeTransform == null)
				{
					compositeTransform = new CompositeTransform();
					element.RenderTransform = compositeTransform;
				}

				compositeTransform.Rotation = angleInDegrees;
			}
		}

		public static void SetScale(UIElement element, double scaleX, double scaleY)
		{
			SetScale(element, scaleX, scaleY, element.RenderTransformOrigin);
		}

		public static void SetScale(UIElement element, double scaleX, double scaleY, Point transformOrigin)
		{
			element.RenderTransformOrigin = transformOrigin;

			ScaleTransform transform = element.RenderTransform as ScaleTransform;
			if (transform != null)
			{
				transform.ScaleX = scaleX;
				transform.ScaleY = scaleY;
			}
			else
			{
				CompositeTransform compositeTransform = element.RenderTransform as CompositeTransform;
				if (compositeTransform == null)
				{
					compositeTransform = new CompositeTransform();
					element.RenderTransform = compositeTransform;
				}

				compositeTransform.ScaleX = scaleX;
				compositeTransform.ScaleY = scaleY;
			}
		}

		public static void SetTranslate(UIElement element, double? translateX, double? translateY)
		{
			SetTranslate(element, translateX, translateY, element.RenderTransformOrigin);
		}

		public static void SetTranslate(UIElement element, double? translateX, double? translateY, Point transformOrigin)
		{
			element.RenderTransformOrigin = transformOrigin;

			TranslateTransform transform = element.RenderTransform as TranslateTransform;
			if (transform != null)
			{
				if (translateX.HasValue)
				{
					transform.X = translateX.Value;
				}
				if (translateY.HasValue)
				{
					transform.Y = translateY.Value;
				}
			}
			else
			{
				CompositeTransform compositeTransform = element.RenderTransform as CompositeTransform;
				if (compositeTransform == null)
				{
					compositeTransform = new CompositeTransform();
					element.RenderTransform = compositeTransform;
				}

				if (translateX.HasValue)
				{
					compositeTransform.TranslateX = translateX.Value;
				}

				if (translateY.HasValue)
				{
					compositeTransform.TranslateY = translateY.Value;
				}
			}
		}

		public static void ClearTranslate(UIElement element)
		{
			TranslateTransform transform = element.RenderTransform as TranslateTransform;
			if (transform != null)
			{
				transform.X = 0.0;
				transform.Y = 0.0;
			}
			else
			{
				CompositeTransform compositeTransform = element.RenderTransform as CompositeTransform;
				if (compositeTransform != null)
				{
					compositeTransform.TranslateX = 0.0;
					compositeTransform.TranslateY = 0.0;
				}
			}
		}

        public static bool IsLandscape(Rect rect)
        {
            return rect.Width > rect.Height;
        }


        public static Size Size(this Rect rect)
        {
#if SILVERLIGHT
            Size size = new Size(rect.Width, rect.Height);
#endif
#if NETFX_CORE
            Size size;
            size.Width = rect.Width;
            size.Height = rect.Height;
#endif
            return size;
        }

        public static LayoutInfo GetOptimumSize(
            Rect space,
            double aspectRatio,
            int countOfItems,
            bool preferLandscape = true,
            double margin = 0.0,
            double minimumWidth = 0.0,
            double minimumHeight = 0.0
            )
        {
            return GetOptimumSize(space, aspectRatio, minimumWidth, minimumHeight, margin, countOfItems, 1, preferLandscape);
        }


        private static LayoutInfo TryOptimumSizeLandscape(
            Rect space,
            double aspectRatio,
            double minimumWidth,
            double minimumHeight,
            double minimumItemMargin,
            int countOfItems,
            int columns)
        {
            // Try prioritize width over height (landscape)
            int landscapeColumns = columns;
            int landscapeRows = (int)Math.Ceiling((double)countOfItems / (double)landscapeColumns);

            int horizontalMarginCount = landscapeColumns - 1;
            int verticalMarginCount = landscapeRows - 1;

            double width = (space.Width - minimumItemMargin * horizontalMarginCount) / landscapeColumns;
            double heightForWidth = width / aspectRatio;

            double totalWidth = width * landscapeColumns + horizontalMarginCount * minimumItemMargin;
            double totalHeightForWidth = heightForWidth * landscapeRows + verticalMarginCount * minimumItemMargin;

            LayoutInfo layoutInfo = new LayoutInfo()
            {
                Width = width,
                Height = heightForWidth,
            };


            if (totalHeightForWidth <= space.Height && totalWidth <= space.Width && heightForWidth >= minimumHeight && width >= minimumWidth)
            {
                // Everything fits in landscape
                layoutInfo.Rows = landscapeRows;
                layoutInfo.Columns = landscapeColumns;
            }

            return layoutInfo;
        }

        private static LayoutInfo TryOptimumSizePortrait(
            Rect space,
            double aspectRatio,
            double minimumWidth,
            double minimumHeight,
            double minimumItemMargin,
            int countOfItems,
            int rows)
        {
            // Try prioritize height over width (portrait)
            int portraitRows = rows;
            int portraitColumns = (int)Math.Ceiling((double)countOfItems / (double)portraitRows);

            int horizontalMarginCount = portraitColumns - 1;
            int verticalMarginCount = portraitRows - 1;

            double height = (space.Height - minimumItemMargin * verticalMarginCount) / portraitRows;
            double widthForHeight =  height * aspectRatio;

            double totalHeight = height * portraitRows + verticalMarginCount * minimumItemMargin;
            double totalWidthForHeight = widthForHeight * portraitColumns + horizontalMarginCount * minimumItemMargin;

            LayoutInfo layoutInfo = new LayoutInfo() {
                Width = widthForHeight,
                Height = height,
            };

                      
            if (totalWidthForHeight <= space.Width && totalHeight <= space.Height && height >= minimumHeight && widthForHeight >= minimumWidth)
            {
                 // Everything fits in portrait
                layoutInfo.Rows = portraitRows;
                layoutInfo.Columns = portraitColumns;
            }

            return layoutInfo;
        }


        private static LayoutInfo GetOptimumSize(
            Rect space,
            double aspectRatio,
            double minimumWidth,
            double minimumHeight,
            double minimumItemMargin,
            int countOfItems,
            int iterationCount,
            bool preferLandscape
            )
        {
            LayoutInfo landscapeInfo;
            LayoutInfo portraitInfo;

            if (preferLandscape)
            {
                landscapeInfo = TryOptimumSizeLandscape(space, aspectRatio, minimumWidth, minimumHeight, minimumItemMargin, countOfItems, iterationCount);
                if (!landscapeInfo.IsEmpty)
                {
                    // Everything fits in landscape
                    return landscapeInfo;
                }

                portraitInfo = TryOptimumSizePortrait(space, aspectRatio, minimumWidth, minimumHeight, minimumItemMargin, countOfItems, iterationCount);
                if (!portraitInfo.IsEmpty)
                {
                    // Everything fits in portrait
                    return portraitInfo;
                }
            }
            else
            {
                portraitInfo = TryOptimumSizePortrait(space, aspectRatio, minimumWidth, minimumHeight, minimumItemMargin, countOfItems, iterationCount);
                if (!portraitInfo.IsEmpty)
                {
                    // Everything fits in portrait
                    return portraitInfo;
                }

                landscapeInfo = TryOptimumSizeLandscape(space, aspectRatio, minimumWidth, minimumHeight, minimumItemMargin, countOfItems, iterationCount);
                if (!landscapeInfo.IsEmpty)
                {
                    // Everything fits in landscape
                    return landscapeInfo;
                }
            }



            if (portraitInfo.Width < minimumWidth || portraitInfo.Height < minimumHeight || 
                landscapeInfo.Width < minimumWidth || landscapeInfo.Height < minimumHeight)
            {
                // No solution
                return null;
            }
            
            return GetOptimumSize(space, aspectRatio, minimumWidth, minimumHeight, minimumItemMargin, countOfItems, iterationCount + 1, preferLandscape);            
        }

        public static Size SafeSize(double width, double height)
        {
#if SILVERLIGHT
            Size size = new Size((!width.IsValid() || width < 0.0) ? 0.0 : width, (!height.IsValid() || height < 0.0) ? 0.0 : height);
#endif
#if NETFX_CORE
            Size size;
            size.Width = (!width.IsValid() || width < 0.0) ? 0.0 : width;
            size.Height = (!height.IsValid() || height < 0.0) ? 0.0 : height;
#endif

            return size;
        }

        public static double GetLength(double lengthOfItem, int numberOfItems, double distanceBetweenTwoItems)
        {
            if (numberOfItems < 0)
            {
                return Double.NaN;
            }

            if (numberOfItems == 0)
            {
                return 0.0;
            }

            if (numberOfItems == 1)
            {
                return lengthOfItem;
            }

            return numberOfItems * lengthOfItem + (numberOfItems - 1) * distanceBetweenTwoItems;
        }
	}
}
